﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using System.Reflection.Metadata;
using System.Security.Cryptography;
using System.Text;
using Newtonsoft;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using WJ.Udp.Tcp.Test.Models;

namespace WJ.Udp.Tcp.Test
{
    /// <summary>
    /// WJ-JSON数据交换协议中验证数据完整性的加签验签帮助类
    /// </summary>
    public class SignHelper
    {
        /// <summary>
        /// 生成签名
        /// </summary>
        /// <param name="sourceJson">原始JSON字符串</param>
        /// <returns></returns>
        public string SignJson(string sourceJson)
        {

            return "";
        }

        public static void InitTargetModel()
        {
            LidarModel lidarModel = new LidarModel();
            lidarModel.AddressNo = "0001";
            lidarModel.AddressDes = "北京_海淀_上地软件园_8号门";
            lidarModel.Num = 2;
            lidarModel.TimeStamp = 1559733442;
            lidarModel.ObjectList.Add(
                new EquipCommonInfo
                {
                    EquipmentNo= null,
                    EquipmentSN= "58458965E48Q",
                    EquipmentName= "1号相机",
                    EquipmentIP= "192.168.1.10",
                    EquipmentStatus= (int)EquipmentStatus.Ok
                });
            lidarModel.ObjectList.Add(
                new EquipCommonInfo
                {
                    EquipmentNo = "0002",
                    EquipmentSN = "59458965E48Q",
                    EquipmentName = "2号相机",
                    EquipmentIP = "192.168.1.11",
                    EquipmentStatus = (int)EquipmentStatus.OffLine,
                    Longitude = 116.39564503788,
                    Latitude = 39.92998577808
                });

            var testModel = new TestModel();
            testModel.appId = "123456";
            testModel.content = "test";
            //Json序列化
            
            var watch = Stopwatch.StartNew();
            watch.Start();
            var jsonStr = JsonConvert.SerializeObject(lidarModel);
            //var jsonStr = JsonConvert.SerializeObject(testModel);
            //var jsonMap = ObjectToMap(lidarModel);
            Dictionary<string, string> kvList = new Dictionary<string, string>();
            //List<KeyValuePair<string, string>> kvList = new List<KeyValuePair<string, string>>();
            var str = CreateSignStr(jsonStr,new List<string> { "Sign"},kvList);
            var md5 = GetMD5Hash(str);
            watch.Stop();
            var ms = watch.ElapsedMilliseconds;
            Console.WriteLine($"一共耗时{ms}ms,{str}\r\n{md5}");
        }

        /// <summary>
        /// 当前时间转换为UTC时间输出毫秒数
        /// </summary>
        /// <param name="time">时间</param>
        /// <returns></returns>
        public static long ConvertDateTimeToUtcms(DateTime time)
        {

            double intResult = 0;

            System.DateTime startTime = TimeZone.CurrentTimeZone.ToLocalTime(new System.DateTime(1970, 1, 1));

            intResult = (time - startTime).TotalSeconds;

            return (int)intResult;

        }

        public static Dictionary<string,string> ConvertObjectToMap(object obj)
        {
            var map = new Dictionary<string, string>();
            Type type = obj.GetType();
            PropertyInfo[] propertyInfos = type.GetProperties(System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Instance);
            foreach (var p in propertyInfos)
            {
                Console.WriteLine(p.Name + "<>" +p.GetValue(obj));
                map.Add(p.Name, p.GetValue(obj)?.ToString());
            }
            return map;
        }

        public static Dictionary<string, object> ObjectToMap(object obj, bool isIgnoreNull = false)
        {
            Dictionary<string, object> map = new Dictionary<string, object>();

            Type t = obj.GetType(); // 获取对象对应的类， 对应的类型

            PropertyInfo[] pi = t.GetProperties(BindingFlags.Public | BindingFlags.Instance); // 获取当前type公共属性

            foreach (PropertyInfo p in pi)
            {
                MethodInfo m = p.GetGetMethod();

                if (m != null && m.IsPublic)
                {
                    // 进行判NULL处理 
                    if (m.Invoke(obj, new object[] { }) != null || !isIgnoreNull)
                    {
                        map.Add(p.Name, m.Invoke(obj,null)); // 向字典添加元素
                    }
                }
            }
            return map;
        }

        /// <summary>
        /// 将json转化成加签使用的字符串
        /// </summary>
        /// <param name="json"></param>
        /// <param name="ignoreProps">忽略的属性</param>
        /// <param name="kvList"></param>
        /// <returns></returns>
        public static void HandleJsonStr(string json,List<string> ignoreProps, List<KeyValuePair<string, string>> kvList)
        {
            
            json = json.Replace("\r\n", string.Empty);
            json = json.Replace("\n", string.Empty);
            //json = json.Replace("[", string.Empty);
            //json = json.Replace("]", string.Empty);
            JObject o = JObject.Parse(json);
            foreach (var x in o)
            {
                if (ignoreProps.Contains(x.Key) || x.Value is null ) continue;
                if (x.Value.GetType() == typeof(JObject))
                {

                    HandleJsonStr(x.Value.ToString(), ignoreProps, kvList);//递归
                }
                else if (x.Value.GetType() == typeof(JArray))
                {
                    var jsonChilds = x.Value.Children();
                    foreach (var child in jsonChilds)
                    {
                        HandleJsonStr(child.ToString(), ignoreProps, kvList);
                    }
                }
                else
                    kvList.Add(new KeyValuePair<string, string> (x.Key, x.Value.ToString() ));//这里x.Key则为键，x.Value为值。可以选择使用字典
            }
        }

        /// <summary>
        /// 将json转化成加签使用的字符串
        /// </summary>
        /// <param name="json">序列化后的字符串</param>
        /// <param name="ignoreProps">要忽略的字段</param>
        /// <param name="kvList">定义字典容器</param>
        /// <returns></returns>
        public static string CreateSignStr(string json, List<string> ignoreProps, Dictionary<string, string> kvList)
        {
            json = json.Replace("\r\n", string.Empty);
            json = json.Replace("\n", string.Empty);
            JObject o = JObject.Parse(json);
            foreach (var x in o)
            {
                if (ignoreProps.Contains(x.Key) || x.Value is null) continue;
                
                else
                    kvList.Add(x.Key, x.Value.ToString());//这里x.Key则为键，x.Value为值。可以选择使用字典
            }
            string[] keys = kvList.Keys.ToArray();
            Array.Sort(keys, string.CompareOrdinal);
            StringBuilder stringBuilder = new StringBuilder();
            foreach (var item in keys)
            {
                stringBuilder.Append(item + "=" + kvList[item].Replace("\r\n", string.Empty) + "&");
            }
            var result = stringBuilder.ToString().Replace(" ",string.Empty).Replace("\"",string.Empty).TrimEnd('&');
            return result;
        }

        public static string GetMD5Hash(string str)
        {
            //就是比string往后一直加要好的优化容器
            StringBuilder sb = new StringBuilder();
            using (MD5CryptoServiceProvider md5 = new MD5CryptoServiceProvider())
            {
                //将输入字符串转换为字节数组并计算哈希。
                byte[] data = md5.ComputeHash(Encoding.UTF8.GetBytes(str));

                //X为     十六进制 X都是大写 x都为小写
                //2为 每次都是两位数
                //假设有两个数10和26，正常情况十六进制显示0xA、0x1A，这样看起来不整齐，为了好看，可以指定"X2"，这样显示出来就是：0x0A、0x1A。 
                //遍历哈希数据的每个字节
                //并将每个字符串格式化为十六进制字符串。
                int length = data.Length;
                for (int i = 0; i < length; i++)
                    sb.Append(data[i].ToString("X2"));

            }
            return sb.ToString();
        }

        //验证
        public static bool VerifyMD5Hash(string str, string hash)
        {
            string hashOfInput = GetMD5Hash(str);
            if (hashOfInput.CompareTo(hash) == 0)
                return true;
            else
                return false;
        }
    }
}
